#region License
//-----------------------------------------------------------------------------
// Copyright (c) 2008, Aaron MacDougall, Daniel Jeffery
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Aaron MacDougall or Daniel Jeffery nor the names of its contributors may
//   be used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System.Reflection;
using System.Diagnostics;
using Agro2DPipeline;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace Agro2D
{
    /// <summary>
    /// A Level can provide its own properties which to load and access.
    /// Levels should define how scene objects/sprites are loaded and whether there should
    /// be any special processing to be done at load time.
    /// </summary>
    public class Level
    {
        #region Fields
        protected string levelName;
        protected string fileName;
        private SpriteManager spriteManager;

        List<Vector2> spawnPoints = new List<Vector2>();
        #endregion

        #region Properties
        public SpriteManager SpriteManager
        {
            get { return this.spriteManager; }
            set { this.spriteManager = value; }
        }

        // Get/Set level's list of generic spawn points
        [ImportAttribute]
        public List<Vector2> SpawnPoints
        {
            get { return this.spawnPoints; }
            set { this.spawnPoints = value; }
        }

        /// <summary>
        ///  Name of Level.
        /// </summary>
        [ImportAttribute] 
        public string Name
        {
            get { return levelName; }
            set { levelName = value; }
        }

        /// <summary>
        ///  Filename of Level.
        /// </summary>
        public string FileName
        {
            get { return fileName; }
            set { fileName = value; }
        }        
        #endregion

        #region Constructors/Destructors
        public Level()
        {
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="info">LevelInfo that holds data about this Level.</param>
        /// <param name="spriteManager">SpriteManager to add Sprites to</param>
        public Level(SpriteManager spriteManager)
        {
            this.spriteManager = spriteManager;
        }
        #endregion

        /// <summary>
        /// Loads sprites into memory.
        /// </summary>
        public virtual void Load(LevelInfo info)
        {
            this.ApplyProperties(info);
            this.LoadSpawnPoints(info);

            // Sprites and animations.
            foreach(SpriteInfo spriteInfo in info.SpriteInfo.Sprites)
            {
                Debug.Assert(spriteInfo.Properties.ContainsKey("Type"), "No type specified in level file to instance");
                if(!spriteInfo.Properties.ContainsKey("Type"))
                {
                    Log.Instance.LogMessage("No type specified in level file to instance. Loading aborted.");
                    return;
                }

                Sprite sprite = SpriteFactory.Instance.CreateTemplateInstance(spriteInfo.Properties["Type"], this.spriteManager);

                SpriteLoader.ApplyStandardProperties(spriteInfo, sprite);
                SpriteLoader.LoadSpriteProperties(spriteInfo, sprite);
                SpriteLoader.LoadCollisionProperties(spriteInfo, sprite);

                if(sprite.CollisionProperties.CollisionType == CollisionType.PerPixel)
                    CollisionManager.Instance.CreateCollisionData(sprite.SpriteProperties.Texture);

                if(sprite.CollisionProperties.CollisionType == CollisionType.Vector)
                {
                    CollisionProperties collProps = sprite.CollisionProperties;

                    // Get slope 
                    float diffY = collProps.LeftVector.Y - collProps.RightVector.Y; // NB: Opposite to normal formula because of opposite 2D co-ords
                    float diffX = collProps.RightVector.X - collProps.LeftVector.X;

                    collProps.Slope = diffY / diffX;
                }

                // Animations. Don't replace with an empty animation.
                if(spriteInfo.Animation != null)
                    sprite.Animation = SpriteLoader.LoadAnimations(spriteInfo.Animation, null);
             
                if(spriteInfo.Properties.ContainsKey("Drawable")) // override?
                    sprite.Drawable = bool.Parse(spriteInfo.Properties["Drawable"]);

                if(sprite.LayerNum == 0)
                    sprite.LayerNum = 3;

                if(sprite.Drawable)
                    this.spriteManager.AddDrawable(sprite, sprite.LayerNum);
                else
                    this.spriteManager.AddInvisible(sprite);
            }           

        }

        /// <summary>
        /// Reloads the current level script and updates the sprites in the scene. 
        /// This includes changes to their properties in their types and the overidden ones.
        /// 
        /// </summary>
        public virtual void Reload(ref LevelInfo info)
        {
            // TO DO: Make sure to include any changes in the sprites types and in the levels.
            // TO DO: Need to reset all the non-scriptable state - have to create and destroy.

            CloseLevel();

            this.ApplyProperties(info);

            // Sprites and animations.
            foreach(SpriteInfo spriteInfo in info.SpriteInfo.Sprites)
            {
                Debug.Assert(spriteInfo.Properties.ContainsKey("Type"), "No type specified in level file to instance");
                if(!spriteInfo.Properties.ContainsKey("Type"))
                {
                    Log.Instance.LogMessage("No type specified in level file to instance. Loading aborted.");
                    return;
                }

                Sprite sprite = SpriteFactory.Instance.CreateTemplateInstance(spriteInfo.Properties["Type"], this.spriteManager);

                SpriteLoader.ApplyStandardProperties(spriteInfo, sprite);
                SpriteLoader.LoadSpriteProperties(spriteInfo, sprite);
                SpriteLoader.LoadCollisionProperties(spriteInfo, sprite);

                // Animations. Don't replace with an empty animation.
                if(spriteInfo.Animation != null)
                    sprite.Animation = SpriteLoader.LoadAnimations(spriteInfo.Animation, null);

                if(spriteInfo.Properties.ContainsKey("Drawable")) // override?
                    sprite.Drawable = bool.Parse(spriteInfo.Properties["Drawable"]);

                if(sprite.LayerNum == 0)
                    sprite.LayerNum = 3;

                if(sprite.Drawable)
                    this.spriteManager.AddDrawable(sprite, sprite.LayerNum);
                else
                    this.spriteManager.AddInvisible(sprite);
            }
        }

        /// <summary>
        /// Clears all of the loaded Sprites in the level. This should almost
        /// always been done when the level has ended.
        /// </summary>
        public virtual void CloseLevel()
        {
            this.spriteManager.Clear();
        }

        /// <summary>
        /// Update any Level based logic.
        /// </summary>
        /// <param name="gameTime">game time</param>
        public virtual void Update(GameTime gameTime)
        {
        }

        /// <summary>
        /// Applies properties to a level.
        /// </summary>
        /// <param name="level"></param>
        public void ApplyProperties(LevelInfo info)
        {
            object[] attributes;
            Type type = this.GetType();
            Dictionary<string, PropertyInfo> props = new Dictionary<string, PropertyInfo>();
            List<string> Keys = new List<string>();
            List<string> Values = new List<string>();

            // Properties of Level.
            foreach(PropertyInfo prop in type.GetProperties())
                props.Add(prop.Name, prop);

            // Data keys.
            foreach(string key in info.Properties.Keys)
                Keys.Add(key);

            // Data values.
            foreach(string value in info.Properties.Values)
                Values.Add(value);

            for(int i = 0; i < info.Properties.Count; i++)
            {
                if(!props.ContainsKey(Keys[i])) continue;
                Debug.Assert(props.ContainsKey(Keys[i]), "An invalid property has been used.");
                attributes = props[Keys[i]].GetCustomAttributes(typeof(ImportAttribute), true);
                // Apply property
                if(attributes.Length > 0)
                {
                    if(props[Keys[i]].PropertyType == typeof(Boolean))
                        props[Keys[i]].SetValue(this, bool.Parse(Values[i]), null);
                    else if(props[Keys[i]].PropertyType == typeof(string))
                        props[Keys[i]].SetValue(this, Values[i], null);
                    else if(props[Keys[i]].PropertyType == typeof(Vector2))
                        props[Keys[i]].SetValue(this, ParseHelper.ParseValue(props[Keys[i]], Values[i]), null);
                    else
                        props[Keys[i]].SetValue(this, Values[i], null);

                }
            }
        }

        public virtual void LoadSpawnPoints(LevelInfo info)
        {
            for(int i = 0; i < info.SpawnPointInfoList.Count; i++)
            {
                string value = info.SpawnPointInfoList[i].Properties["position"];
                string[] split;
                char[] separator = { ',' };
                float x;
                float y;

                // Split numbers into individual strings
                split = value.Split(separator);

                Debug.Assert(split.Length == 2);

                // Convert to floats
                x = float.Parse(split[0]);
                y = float.Parse(split[1]);

                Vector2 vec = new Vector2(x, y);

                this.spawnPoints.Add(vec);
            }
        }

        public virtual object Clone()
        {
            Level clonedObject = new Level(this.SpriteManager);
            clonedObject.levelName = this.levelName;
            clonedObject.fileName = this.fileName;

            return clonedObject;
        }
    }    

    /// <summary>
    /// Reads LevelInfo .xnb - XNA content.
    /// </summary>
    public class LevelReader : ContentTypeReader<LevelInfo>
    {
        /// <summary>
        /// Takes the Level input, and converts it to a format that can be used to
        /// at runtime.
        /// </summary>
        protected override LevelInfo Read(ContentReader input, LevelInfo instance)
        {
            instance = new LevelInfo();
            
            instance.TypeName = input.ReadString();

            // Check how many Level properties we have defined.
            int propsCount = input.ReadInt32();
            for(int i = 0; i < propsCount; i++)
            {
                string key = input.ReadString();
                string value = input.ReadString();
                instance.Properties.Add(key, value);
            }

            SpriteLoader.ReadSprites(input, instance.SpriteInfo);
            SpriteLoader.ReadSpawnPoints(input, instance.SpawnPointInfoList);

            return instance;
        }

    }

}